using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Urban_Flash.Dependencies;
using FarseerPhysics;
using FarseerPhysics.Dynamics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using Urban_Flash.Player.States;

namespace Urban_Flash.Player
{
    [Flags]
    public enum PlayerFlags { None = 0, OnGround = 1, Moving = 2}
    public class Player : PhysicsObject
    {
        #region Constuctors
        public Player(World physics, Vector2 position, Camera2D camera)
            : base(physics, position, 64, 64, 3)
        {
            Visible = true;
            TintColor = Color.White;
            FlipHorizontally = false;
            CameraBand = new PullyBand(physics, ref this.body, ref camera.body);

            InitStateMachine();
            InitMovement();

        }
        public Player(World physics, Vector2 position, float width, float height, Camera2D camera, Texture2D texture, float mass)
            : base(physics, position, width, height, mass, texture)
        {
            Visible = true;
            TintColor = Color.White;
            FlipHorizontally = false;
            CameraBand = new PullyBand(physics, ref this.body, ref camera.body);

            InitStateMachine();
            InitMovement();

        }
        #endregion

        #region Methods

        public override void load(ContentManager cm)
        {
            //Load Texture and animations TODO Load animations
            this.texture = cm.Load<Texture2D>("World Textures/squaretex");

            //Set up orgin based on texture
            origin = new Vector2(texture.Width / 2, texture.Height / 2);
        }

        void InitStateMachine()
        {
            Idle iState = new Idle(this);
            iState.AddTransition(Transition.StartRunning, StateID.Run);
            StateMachine.AddState(iState);

            Run rState = new Run(this);
            rState.AddTransition(Transition.NoActions, StateID.Idle);
            StateMachine.AddState(rState);
        }

        void InitMovement()
        {
           speedLevel = 0;
           maxSpeedLevel = 3;
           SpeedTimer = 0.0f;

           maxSpeedUpgradeTimeLength[0] = 5.0f;
           maxSpeedUpgradeTimeLength[1] = 5.0f;
           maxSpeedUpgradeTimeLength[2] = 5.0f;

           MaxSpeed[0] = 5.5f;
           MaxSpeed[1] = 1.0f;
           MaxSpeed[2] = 1.5f;

           MovementForce[0] = 0.5f;
           MovementForce[1] = 0.25f;
           MovementForce[2] = 2.08f;

           body.LinearDamping = PhysicsHelper.GetLinearDamping(body.Mass, 0.01f, MaxSpeed[speedLevel], Vector2.UnitX * MovementForce[speedLevel]);
        }

        public override void Update(GameTime gameTime)
        {
            //Update the base class
            base.Update(gameTime);

            //Handle the input for this player
            HandleInput(gameTime);

            //Update States
            if(StateMachine.CurrentState != null)
            StateMachine.CurrentState.Execute();

            //Update GamePlaye Modules
            UpdateSpeedLevel(gameTime);
        }

        public override void Draw(SpriteBatch sb)
        {
            if (!Visible) return;
            SpriteEffects effects = SpriteEffects.None;
            if (FlipHorizontally) effects |= SpriteEffects.FlipHorizontally;

            //Convert Position to Pixels to be used for display
            Vector2 positonPixels = ConvertUnits.ToDisplayUnits(body.Position);
            sb.Draw(texture, positonPixels, null, TintColor, body.Rotation, origin, 1.0f, effects, 0);
        }

        public void HandleInput(GameTime gameTime)
        {
            gamepadInput.Update(gameTime);
            if (gamepadInput.GetActionKey(ActionKey.RunLeft))
            {
                PlayerStateEventArgs pEvent = new PlayerStateEventArgs((float)gameTime.TotalGameTime.TotalSeconds, ActionKey.RunLeft);
                StateMachine.CurrentState.OnPlayerInputEvent(this, pEvent);
                TurnFlagOn(PlayerFlags.Moving);
            }
            else if (gamepadInput.GetActionKey(ActionKey.RunRight))
            {
                PlayerStateEventArgs pEvent = new PlayerStateEventArgs((float)gameTime.TotalGameTime.TotalSeconds, ActionKey.RunRight);
                StateMachine.CurrentState.OnPlayerInputEvent(this, pEvent);
                TurnFlagOn(PlayerFlags.Moving);
            }
            else
            {
                StateMachine.PerformTransition(Transition.NoActions);
                TurnFlagOff(PlayerFlags.Moving);
            }
        }

        public bool IsFlagSet(PlayerFlags f)
        {
            return ((info & f) == f);
        }
        public void TurnFlagOff(PlayerFlags f)
        {
            info &= ~f;
        }
        public void TurnFlagOn(PlayerFlags f)
        {
            info |= f;
        }

        #endregion

        #region Gameplay Modules

        public void UpdateCameraband(GameTime gameTime)
        {

        }

        public void UpdateSpeedLevel(GameTime gameTime)
        {
            float currentSpeed = Math.Abs(body.LinearVelocity.X);
            if (currentSpeed >= MaxSpeed[speedLevel])
            {
                SpeedTimer += gameTime.TotalGameTime.Milliseconds * 0.001f;
                if (SpeedTimer > maxSpeedUpgradeTimeLength[speedLevel])
                {
                    SpeedTimer = 0.0f;
                    TurnFlagOff(PlayerFlags.Moving);

                    speedLevel += 1;
                    if (speedLevel >= maxSpeedLevel) speedLevel = maxSpeedLevel - 1;
                    body.LinearDamping = PhysicsHelper.GetLinearDamping(body.Mass, gameTime.TotalGameTime.Milliseconds * 0.001f, MaxSpeed[speedLevel], Vector2.UnitX * MovementForce[speedLevel]);
                }
            }
            else SpeedTimer = 0.0f;

            //TODO: Implentment Slowdown functionality
            if (!IsFlagSet(PlayerFlags.Moving))body.LinearVelocity.Normalize();
               if (body.LinearVelocity.X > 0)
                    body.LinearDamping = PhysicsHelper.GetLinearDamping(body.Mass, gameTime.TotalGameTime.Milliseconds * 0.001f, 0.0f, -(Vector2.UnitX * MovementForce[0]));
                else
                    body.LinearDamping = PhysicsHelper.GetLinearDamping(body.Mass, gameTime.TotalGameTime.Milliseconds * 0.001f, 0.0f, Vector2.UnitX * MovementForce[0]);

            {
               
            }

        }

        /// <summary>
        /// Move the player
        /// </summary>
        /// <param name="force">the force the player is moved by. (in meters per second)</param>
        public void Move (ActionKey A, float time)
        {
            //Fails if an action other than RunLeft is passed in. it assumes RunRight is passed in instead
            int directionModifier = A == ActionKey.RunLeft ? -1 : 1;
            Vector2 force = (Vector2.UnitX * MovementForce[speedLevel]*directionModifier)* time;
            body.ApplyForce(force);
        }
        #endregion

        #region Fields
        /// <summary>
        /// The color to tint the item's texture with (use white for no tint).
        /// </summary>
        public Color TintColor;

        /// <summary>
        /// If true, the texture is flipped horizontally when drawn.
        /// </summary>
        public bool FlipHorizontally;

        /// <summary>
        /// The band attaching the camera to the player
        /// </summary>
        PullyBand CameraBand;

        /// <summary>
        /// The current level of speed
        /// </summary>
        int speedLevel = 0;

        /// <summary>
        /// How many speedlevels does the player have
        /// </summary>
        int maxSpeedLevel = 3;

        /// <summary>
        /// How long have you been at max speed
        /// </summary>
        float SpeedTimer = 0.0f;

        /// <summary>
        /// The amount of time you need to be at max speed til you upgrade to the next level
        /// </summary>
        Dictionary<int, float> maxSpeedUpgradeTimeLength = new Dictionary<int,float>();

        /// <summary>
        /// Depending on the speedLevel Determines how fast the player can move
        /// </summary>
        Dictionary<int, float> MaxSpeed = new Dictionary<int,float>();

        /// <summary>
        /// How much force to apply to the players movement based on the speedLevel
        /// </summary>
        Dictionary<int, float> MovementForce = new Dictionary<int,float>();

        /// <summary>
        /// FSM that contorls all the player sates
        /// </summary>
        public FiniteStateMachine StateMachine = new FiniteStateMachine();

        /// <summary>
        /// Handle Input
        /// </summary>
        public ActionKeyInput gamepadInput = new ActionKeyInput();

        /// <summary>
        /// Returns what flags are on
        /// </summary>
        public PlayerFlags info;

        #endregion
    }

    public delegate void PlayerStateEventHandler(object sender, PlayerStateEventArgs e);
    public class PlayerStateEventArgs : EventArgs
    {
        #region Fields
        public float timeStamp; //The current time stamp of when this event was sent.
        public ActionKey action; //what action is the player doing thats sending this event
        #endregion

        #region Constructors
        public PlayerStateEventArgs(float time, ActionKey action)
        {
            this.timeStamp = time;
            this.action = action;
        }
        #endregion
    }

}
